#pragma once

#include "bin_istream.hpp"
#include "bin_ostream.hpp"





namespace boostext
{
	namespace iostreams
	{
		using namespace boost::iostreams;

		class bin_iostream
			: public bin_istream
			, public bin_ostream
		{
		public:

			virtual ~bin_iostream() {}



			/**
			 * Set the endianess of the stream.
			 */
			virtual void setEndian(endian_ordering ordering) = 0;

			/**
			 * Get the endianess of the stream.
			 */
			virtual endian_ordering endian() const = 0;



			/**
			 * Get the current position of the stream.
			 */
			virtual std::streamsize pos() = 0;

			/**
			 * Get the total size of the stream.
			 */
			virtual std::streamsize size() = 0;



			/**
			 * Advances the read/write head to pos, where pos
			 * is the relative offset to the start of the stream.
			 */
			virtual void seek(std::streamsize pos) = 0;

			/**
			 * Advances the read/write head by off characters,
			 * where the offset is calculated from:
			 *  - the start of the sequence if way == ios_base::beg
			 *  - the current position if way == ios_base::cur
			 *  - the end of the sequence if way == ios_base::end
			 */
			virtual void seek(stream_offset off, std::ios_base::seekdir way) = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device, bool direct /*= true*/>
		class bin_iostream_base
			: public bin_iostream
		{
		public:

			typedef typename category_of<Device>::type    mode;
			typedef typename char_type_of<Device>::type   char_type;

		public:

			void write(const void* data, std::streamsize n)
			{
				if(n == 0)
					return;

				Device::pair_type p = (*this)->output_sequence();
				std::streamsize w = std::min(left(), n);
				seek(w, std::ios_base::cur);

				if(w != n)
					BOOSTEXT_THROW(ios_failure("Couldn't write requested size"));

				::memcpy(p.first, data, n);
			}

			void read(void* data, std::streamsize n)
			{
				if(n == 0)
					return;

				Device::char_type* start = (*this)->input_sequence().first;
				std::streamsize r = std::min(left(), n);
				if(r != n)
					BOOSTEXT_THROW(ios_failure("Couldn't read requested size"));

				::memcpy(data, &start[pos()], n);

				seek(r, std::ios_base::cur);
			}

			const void* directSequence(std::streamsize n)
			{
				boost::iostreams::stream_offset pos = this->pos();
				boost::iostreams::stream_offset p = pos + n;
				verify_ios_pos(p, (stream_offset)0, (stream_offset)size());
				Device::pair_type pair = (*this)->input_sequence();
				return &pair.first[pos];
			}

			std::streamsize size()
			{
				Device::pair_type p = (*this)->output_sequence();
				return p.second - p.first;
			}

			virtual Device& operator*() = 0;
			virtual const Device& operator*() const = 0;
			virtual Device* operator->() = 0;
			virtual const Device* operator->() const = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device>
		class bin_iostream_base<Device,/*bool direct =*/ false>
			: public bin_iostream
		{
		public:

			typedef typename category_of<Device>::type    mode;
			typedef typename char_type_of<Device>::type   char_type;

		public:

			void write(const void* data, std::streamsize n)
			{
				std::streamsize w = (*this)->write(reinterpret_cast<const char*>(data), n);
				seek(0, std::ios_base::cur);

				if(w != n)
					BOOSTEXT_THROW(ios_failure("Couldn't write requested size"));
			}

			void read(void* data, std::streamsize n)
			{
				if(n == 0)
					return;

				std::streamsize r = (*this)->read(reinterpret_cast<char*>(data), n);
				seek(0, std::ios_base::cur);

				if(r != n)
					BOOSTEXT_THROW(ios_failure("Couldn't read requested size"));
			}

			const void* directSequence(std::streamsize n)
			{
				BOOSTEXT_THROW(not_implemented_exception());
			}

			std::streamsize size()
			{
				BOOSTEXT_THROW(not_implemented_exception());
			}

			virtual Device& operator*() = 0;
			virtual const Device& operator*() const = 0;
			virtual Device* operator->() = 0;
			virtual const Device* operator->() const = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
